package com.lhz.project.system.task;

import com.lhz.CustomizeApplicationContext;
import com.lhz.common.constant.task.TaskRunnableConstant;
import com.lhz.common.utils.ip.IpUtils;
import com.lhz.common.utils.task.CronUtils;
import com.lhz.common.utils.task.JobInvokeUtil;
import com.lhz.framework.exception.ServiceException;
import com.lhz.project.system.mapper.task.TaskMapper;
import com.lhz.project.system.model.entity.task.Task;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Author: LiHuaZhi
 * @Date: 2021/2/29 15:39
 * @Description:
 **/
@Slf4j
public class TaskRunnable implements Runnable {

    /**
     * 创建任务时，传递任任务id
     */
    private final String id;

    public TaskRunnable(String id) {
        this.id = id;
    }

    // 是否进行校验时间差，第一次执行任务时，不校验时间差
    private boolean checkTime = false;

    @Override
    public void run() {
        // 获取task
        Task currentTask = CustomizeApplicationContext.getBean(TaskMapper.class).selectTaskById(id);

        //获取当前执行时间戳
        long currentTime = System.currentTimeMillis();


        try {
            //判断任务执行时间和实际时间差
            LocalDateTime nextRunTime = currentTask.getNextRunTime();
            log.info("任务：{}，当前：{}，下次：{}", id, new Date(), nextRunTime);
            // 时间差
            long diffTime = Math.abs(currentTime - nextRunTime.toInstant(ZoneOffset.of("+8")).toEpochMilli());


            //执行时，允许200ms误差
            if (diffTime > 200 && checkTime) {
                String msg = "任务执行异常，时间节点错误！";
                //开发中出现了错误情况，可以采用发生邮箱提醒给开发者
                log.error(msg);
                // 获取IP
                String iP = "IP：" + IpUtils.getHostIp();
                // 抛出异常记录错误日志
                throw new ServiceException(msg + iP);
            }
            //通过表达式找到需要执行的方法
            String invokeTarget = currentTask.getInvokeTarget();

            String[] split = invokeTarget.split("\\.");
            //获取bean
            String beanName = split[0];
            // 获取调用方法
            String methodName = split[1];
            // 获取参数
            List<Object[]> methodParams = new ArrayList<>();
            // 默认第一个参数 加上 id 参数
            methodParams.add(0, new Object[]{currentTask.getInvokeParams(), String.class});

            // 通过反射找到对应执行方法
            Object bean = CustomizeApplicationContext.getBean(beanName);
            Method method = bean.getClass().getDeclaredMethod(methodName, JobInvokeUtil.getMethodParamsType(methodParams));
            // 执行任务
            long startTime = System.currentTimeMillis();
            method.invoke(bean, JobInvokeUtil.getMethodParamsValue(methodParams));

            // 更新任务
            updateTask(currentTask);

            // 记录日志
            TaskLogRecord.recordTaskLog(id, startTime, null);

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            // 更新任务
            updateTask(currentTask);
            // 出现异常记录异常日志，并且可以发生邮箱给开发者
            TaskLogRecord.recordTaskLog(id, 0, e);
        } finally {
            // 当任务执行完成后，后续开启时间校验
            checkTime = true;
        }

    }


    private void updateTask(Task currentTask) {
        String taskId = currentTask.getId();
        if (TaskRunnableConstant.TASK_MAP.get(taskId) != null) {
            String cron = currentTask.getCronExpression();
            String invokeTarget = currentTask.getInvokeTarget();
            LocalDateTime nextRunTime = currentTask.getNextRunTime();

            // 查询执行周期
            LocalDateTime nextTime = CronUtils.nextCurrentTime(cron);

            //修改任务状况为执行中
            Task task = new Task();
            task.setId(taskId);
            task.setCronExpression(cron);
            task.setInvokeTarget(invokeTarget);
            task.setNextRunTime(nextTime);
            task.setSituation(1);//执行中

            CustomizeApplicationContext.getBean(TaskMapper.class).updateById(task);

            log.info("更新任务执行情况!");
        }
    }
}
